package simpleHttpClient;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Random;

/**
 * This class takes the input stream and finds relevant data for this lab and
 * stores them in variables
 * 
 * If the encoding type is 'Chunked', then the program will read in chunks
 * 
 * @author Scott Wendlandt
 * Rebecca Chandler
 * Li Weihan
 */
public class Response {
	// The header of the Response
	private String header = "";
	// In the format of "'type'/'something'
	//private String contentType = "";
	// Currently for Chunked or Not-Chunked
	//private String encodingType = "";
	// The body of the response in Strings which are bytes
	//private String entityBody = "";
	// The Content-Disposition field of the sample form:
	//Content-Disposition: attachment; filename="fname.ext"
	//private String contentDisposition = "";
	//the Content-Length field
	//private int contentLength = 0;
	// the response status code
	private String statusLine;
	//keeps track of whether the entity body has been read
	private boolean bodyHasBeenRead = false;
	//file name
	private String fileName = "";
	//input stream to read the response from
	private InputStream inputStream;
	// The Carriage Return Line Feed
	private final String CRLF = "\r\n";
	// Header fields specified with
	// key int: <header field name>.hashCode()
	// value String: <field value>
	private HashMap<Integer, String> fieldsHash;

	/**
	 * Constructor for the Response class.
	 * Gathers information on the header and entity body. 
	 * Decodes entity body if chunked
	 * @param input The inputStream gained through the socket connected to the server
	 */
	public Response(InputStream input) {
		inputStream = input;
		fieldsHash = new HashMap<Integer, String>();

		// To determine when the header is over	
		boolean headerEnd = false;
		// The current line being worked on
		String line = "";

		try {
			// Read first line of header
			statusLine = readLine();
			header += statusLine + CRLF;

			// read the fields of the header
			String[] fieldSplit;
			while (!headerEnd) {	
				line = readLine();
				header += line + CRLF;

				if ((line == null) || (line.equals(""))) {
					// A CRLF by itself indicates the end of the header
					headerEnd = true;

				} else {
					fieldSplit = line.split(":", 2);
					fieldsHash.put(fieldSplit[0].trim().hashCode(), fieldSplit[1].trim());
				}
				/*
				 * Replaced with HashMap
				if (line.contains(CRLF)) {
					// Assemble the header line by line
					header += line;
					if (line.contains("Content-Type:")) {
						String[] subString = line.split(";");
						contentType = subString[0].substring(line.indexOf(':') + 2).trim();
					}
					if (line.contains("Transfer-Encoding:")) {
						encodingType = line.substring(line.indexOf(':') + 2, line.length()-2);
					}
					if (line.contains("Content-Disposition:")) {
						contentDisposition = line.substring(line.indexOf(':') + 2, line.length()-2);
					}
					if (line.contains("Content-Length:")) {
						contentLength = Integer.parseInt(line.substring(line.indexOf(':') + 2, line.length()-2).trim());
					}
					if (line.contains("200 OK")){
						this.status = 200;
					}
					line = "";
				}*/
			}
		} catch (IOException e) {
			System.out.println("Error reading in the header");
		}
	}

	/**
	 * Determines whether the the entity body is chunked or not
	 * @param fo The passed in fileoutputStream 
	 */
	public void readEntityBody(FileOutputStream fo){
		//read in the entity body if it has not yet been read and the parameter fo is not null
		if((!bodyHasBeenRead) && (fo != null)){

			String encodingType = fieldsHash.get(new String("Transfer-Encoding").hashCode());
			// if the encoding type is specified
			if(encodingType != null){
				if(encodingType.trim().equals("chunked")){
					readChunked(fo);
				} else {
					System.out.println("Transfer-Encoding type not recognized.  Entity body has not been read.");
				}

				// if the encoding type is not specified
			}else {
				readNotChunked(fo);
			}
			bodyHasBeenRead = true;
		}
		try {
			fo.flush();
			fo.close();
		} catch (IOException e) {
			System.out.println("Error closing FileOutputStream.");
		}
	}

	/**
	 * Read in the chunked entity body
	 * @param fileOutputStream The output stream that will write to the file.
	 */
	private void readChunked(FileOutputStream fileOutputStream){
		// To continue the loop if the stream is still going
		boolean cont = true;
		// The hex number at the beginning of a chunk
		String hexNum = "";
		// chunk length
		int chunkLength;

		do {
			try {
				// Read in the hex number representing the number of bytes;
				hexNum = readLine();

				if (!hexNum.equals("0")) {
					chunkLength = Integer.parseInt(hexNum, 16);

					readToOutputStream(chunkLength, fileOutputStream);

					// To get rid of the CRLF at the end of the chunk
					readLine();
				} else {
					// If the line is = 0. Signifying the end of stream
					//input.close();
					cont = false;
				}
			} catch (IOException e) {
				System.out.println("Reading bytes failed.");
			}
		} while (cont);
	}

	/**
	 * Read in a non-chunked entity body
	 * @param fileOutputStream The output stream that will write to the file.
	 */
	private void readNotChunked(FileOutputStream fileOutputStream){
		try{
			readToOutputStream(Integer.parseInt(fieldsHash.get(new String("Content-Length").hashCode())), fileOutputStream);
		} catch (IOException e) {
			System.out.println("Error writing to Output Stream.");
		}
	}

	private void readToOutputStream(int numOfBytesToRead, FileOutputStream fileOutputStream) throws IOException{
		int bytesRead = 0;
		int bytesInBuffer = 0;
		byte[] buffer;

		while (bytesRead < numOfBytesToRead) {
			buffer = new byte[numOfBytesToRead
			                  - bytesRead];
			bytesInBuffer = inputStream.read(buffer);
			bytesRead += bytesInBuffer;
			fileOutputStream.write(buffer, 0, bytesInBuffer);
		}
	}

	/**
	 * Reads the next line from the inputStream (bytes are read as chars)
	 * @return
	 * @throws IOException 
	 */
	private String readLine() throws IOException{
		String line = "";

		while(!(line.endsWith(CRLF))){
			byte[] thisByte = new byte[1];
			int bytesRead = 0;

			while(bytesRead == 0){
				bytesRead = inputStream.read(thisByte);
			}
			line += new String(thisByte);
		}
		return line.substring(0, line.length() - 2);
	}

	/**
	 * NOT USED
	 * Returns the entityBody
	 * @return returns the full entityBody received from the web server
	 */
	/*public String getEntityBody() {
		return entityBody;
	}*/

	/**
	 * Returns the Content-Type.  Null if no Content-Type was indicated.
	 * @return returns the type of the content in the format of "'type'/'something'"
	 */
	public String getContentType() {
		return fieldsHash.get(new String("Content-Type").hashCode());
	}

	/**
	 * From the Content Type in the header, determines what file extension this should be saved as
	 * @return returns the file type it should be saved as
	 */	
	public String getFileExt() {
		String contentType = getContentType();

		// takes off optional part after semi-colon
		if(contentType.contains(";")){
			contentType = contentType.substring(0,contentType.indexOf(";"));
		}

		//this trim fixes a newline that appears at the end of the extension when the content-type is the last field read from the response
		return "." + contentType.substring(contentType.indexOf('/')+1);
	}

	/**
	 * Returns the string which is the header of the response
	 * @return returns the header
	 */
	public String toString() {
		return header;
	}

	/**
	 * Returns the Content-Disposition.  Null if no Content-Disposition was indicated.
	 * @return
	 */
	public String getContentDisposition() {
		return fieldsHash.get(new String("Content-Disposition").hashCode());
	}

	/**
	 * Assembles the name of the file to save it to and the extension
	 * @return The filename
	 */
	public String getFileName() {
		String contentDisposition = getContentDisposition();
		if((contentDisposition != null) && (contentDisposition.contains("filename:"))){
			fileName = contentDisposition.substring(contentDisposition.indexOf('"'),contentDisposition.length()-1);
		} else if (fileName.equals("")){
			byte[] bytes = new byte[7];
			new Random().nextInt(999);
			fileName = "" + (new Random().nextInt(999)) + getFileExt();
		}
		return fileName;
	}

	public String getStatusLine() {
		return statusLine;
	}
}
